module wb_ddr3_bridge(
        input           i_clk,
        input           i_rst,
        input   [ 30:2] i_wb_adr,
        input   [  3:0] i_wb_sel,
        input           i_wb_we,
        output  [ 31:0] o_wb_dat,
        input   [ 31:0] i_wb_dat,
        input           i_wb_cyc,
        input           i_wb_stb,
        output          o_wb_ack,

        input           i_instr_read,
        input   [ 30:2] i_instr_addr,
        output          o_instr_ack,
        output  [ 31:0] o_instr_data,

        output  [ 27:0] o_app_addr,
        output  [  2:0] o_app_cmd,
        output          o_app_en,
        input           i_app_rdy,
        input   [511:0] i_app_rd_data,
        /* verilator lint_off UNUSED */
        input           i_app_rd_data_end,
        /* verilator lint_on UNUSED */
        input           i_app_rd_data_valid,
        output  [511:0] o_app_wdf_data,
        output          o_app_wdf_end,
        output  [ 63:0] o_app_wdf_mask,
        input           i_app_wdf_rdy,
        output          o_app_wdf_wren
);

localparam IDLE         = 0;
localparam RD_WAIT_RDY  = 1;
localparam RD_WAIT_DV   = 2;
localparam RD_ACK       = 3;
localparam WR_WAIT_FRDY = 4;
localparam WR_WAIT_RDY  = 5;
localparam WR_ACK       = 6;
localparam RD_EN        = 7;
localparam WR_EN        = 8;
localparam EX_WAIT_RDY  = 9;
localparam EX_WAIT_DV   = 10;
localparam EX_EN        = 11;

reg [11:0] state;

reg     [  5:2] wb_adr;
reg     [ 30:3] app_addr; // bottom 3 bits always 0
reg     [ 31:0] wb_dat_o;
reg     [511:0] app_wdf_data;
reg     [ 63:0] app_wdf_mask;
reg             app_en;
reg     [  2:0] app_cmd;
reg             app_wdf_wren;
reg             wb_ack;

reg     [511:0] dcache;
reg     [ 30:6] dcache_tag;
reg             dcache_valid;

// 4 ways, 16 sets
reg     [511:0] icache       [63:0];
reg     [30:10] icache_tag   [63:0];
reg     [ 63:0] icache_valid;

reg [1:0] icache_block_no;
reg [2:0] plru_tree;

reg        instr_ack;
reg [31:0] instr_data;

assign o_instr_ack = instr_ack;
assign o_instr_data = instr_data;

assign  o_app_addr = app_addr;
assign  o_wb_dat = wb_dat_o;
assign  o_app_wdf_data = app_wdf_data;
assign  o_app_wdf_mask = app_wdf_mask;
assign  o_app_cmd = app_cmd;
assign  o_app_wdf_end = 1'b1;
assign  o_wb_ack = wb_ack;
assign  o_app_en = app_en;
assign  o_app_wdf_wren = app_wdf_wren;

/*ila_0 u_ila_0(
  .clk(i_clk),
  .probe0(i_app_rd_data),
  .probe1(i_app_rd_data_valid),
  .probe2(wb_ack),
  .probe3(wb_adr),
  .probe4({1'b0,i_wb_adr,2'b0})
);*/

always @(posedge i_clk) begin
  if (i_rst) begin
    state <= 1<<IDLE;
    app_en <= 1'b0;
    app_wdf_wren <= 1'b0;
    wb_ack <= 1'b0;
    dcache_valid <= 1'b0;
    icache_valid <= 64'b0;
  end else begin
    case (1'b1)
      state[IDLE]: begin
        if (i_instr_read) begin
          case (1'b1)
            icache_valid[{i_instr_addr[9:6],2'd0}] & i_instr_addr[30:10] == icache_tag[{i_instr_addr[9:6],2'd0}]: begin instr_ack <= 1'b1; instr_data <= icache[{i_instr_addr[9:6],2'd0}][i_instr_addr[5:2]*32+:32]; {plru_tree[2],plru_tree[0]} <= 2'b11; end
            icache_valid[{i_instr_addr[9:6],2'd1}] & i_instr_addr[30:10] == icache_tag[{i_instr_addr[9:6],2'd1}]: begin instr_ack <= 1'b1; instr_data <= icache[{i_instr_addr[9:6],2'd1}][i_instr_addr[5:2]*32+:32]; {plru_tree[2],plru_tree[0]} <= 2'b10; end
            icache_valid[{i_instr_addr[9:6],2'd2}] & i_instr_addr[30:10] == icache_tag[{i_instr_addr[9:6],2'd2}]: begin instr_ack <= 1'b1; instr_data <= icache[{i_instr_addr[9:6],2'd2}][i_instr_addr[5:2]*32+:32]; {plru_tree[2],plru_tree[1]} <= 2'b01; end
            icache_valid[{i_instr_addr[9:6],2'd3}] & i_instr_addr[30:10] == icache_tag[{i_instr_addr[9:6],2'd3}]: begin instr_ack <= 1'b1; instr_data <= icache[{i_instr_addr[9:6],2'd3}][i_instr_addr[5:2]*32+:32]; {plru_tree[2],plru_tree[1]} <= 2'b00; end
            default: begin // cache miss
              instr_ack <= 1'b0;
              app_addr <= {i_instr_addr[30:6], 3'b0};
              wb_adr <= i_instr_addr[5:2];
              state <= 1<<EX_WAIT_RDY;
              case (1'b0)
                icache_valid[{i_instr_addr[9:6],2'd0}]: icache_block_no <= 2'd0;
                icache_valid[{i_instr_addr[9:6],2'd1}]: icache_block_no <= 2'd1;
                icache_valid[{i_instr_addr[9:6],2'd2}]: icache_block_no <= 2'd2;
                icache_valid[{i_instr_addr[9:6],2'd3}]: icache_block_no <= 2'd3;
                default: begin
                  icache_block_no <= {plru_tree[2], plru_tree[2] ? plru_tree[1] : plru_tree[0]};
                end
              endcase
            end
          endcase
        end else if (i_wb_cyc & i_wb_stb) begin
          if (i_wb_we) begin
            // write
            // update cache
            if (dcache_valid & i_wb_adr[30:6] == dcache_tag) begin
              dcache[i_wb_adr[5:2]*32+:32] <= i_wb_dat;
            end
            app_wdf_wren <= 1'b1;
            state <= 1<<WR_WAIT_FRDY;
            app_wdf_data <= {16{i_wb_dat}};
            app_wdf_mask[ 0+:4] <= i_wb_adr[5:2] == 4'b0000 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[ 4+:4] <= i_wb_adr[5:2] == 4'b0001 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[ 8+:4] <= i_wb_adr[5:2] == 4'b0010 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[12+:4] <= i_wb_adr[5:2] == 4'b0011 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[16+:4] <= i_wb_adr[5:2] == 4'b0100 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[20+:4] <= i_wb_adr[5:2] == 4'b0101 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[24+:4] <= i_wb_adr[5:2] == 4'b0110 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[28+:4] <= i_wb_adr[5:2] == 4'b0111 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[32+:4] <= i_wb_adr[5:2] == 4'b1000 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[36+:4] <= i_wb_adr[5:2] == 4'b1001 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[40+:4] <= i_wb_adr[5:2] == 4'b1010 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[44+:4] <= i_wb_adr[5:2] == 4'b1011 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[48+:4] <= i_wb_adr[5:2] == 4'b1100 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[52+:4] <= i_wb_adr[5:2] == 4'b1101 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[56+:4] <= i_wb_adr[5:2] == 4'b1110 ? ~i_wb_sel : 4'b1111 ;
            app_wdf_mask[60+:4] <= i_wb_adr[5:2] == 4'b1111 ? ~i_wb_sel : 4'b1111 ;
          end else begin
            // read
            if (dcache_valid & i_wb_adr[30:6] == dcache_tag) begin
              wb_ack <= 1'b1;
              wb_dat_o <= dcache[i_wb_adr[5:2]*32+:32];
              state <= 1<<RD_ACK;
            end else begin
              state <= 1<<RD_WAIT_RDY;
            end
          end
          app_addr <= {i_wb_adr[30:6], 3'b0};
          wb_adr <= i_wb_adr[5:2];
        end
      end
      // Read operation
      state[RD_WAIT_RDY]: begin
        if (i_app_rdy) begin
          app_en <= 1'b1;
          app_cmd <= 3'b001;
          state <= 1<<RD_EN;
        end
      end
      state[RD_EN]: begin
        app_en <= 1'b0;
        if (i_app_rdy) begin
          state <= 1<<RD_WAIT_DV;
        end else begin
          state <= 1<<RD_WAIT_RDY;
        end
      end
      state[RD_WAIT_DV]: begin
        if (i_app_rd_data_valid) begin
          wb_ack <= 1'b1;
          wb_dat_o <= i_app_rd_data[wb_adr*32+:32];
          dcache <= i_app_rd_data;
          dcache_tag <= app_addr[30:6];
          dcache_valid <= 1'b1;
          state <= 1<<RD_ACK;
        end
      end
      state[RD_ACK]: begin
        wb_ack <= 1'b0;
        state <= 1<<IDLE;
      end
      // Write operation
      state[WR_WAIT_FRDY]: begin
        if (i_app_wdf_rdy) begin
          app_wdf_wren <= 1'b0;
          state <= 1<<WR_WAIT_RDY;
        end
      end
      state[WR_WAIT_RDY]: begin
        if (i_app_rdy) begin
          app_en <= 1'b1;
          app_cmd <= 3'b000;
          state <= 1<<WR_EN;
        end
      end
      state[WR_EN]: begin
        app_en <= 1'b0;
        if (i_app_rdy) begin
          wb_ack <= 1'b1;
          state <= 1<<WR_ACK;
        end else begin
          state <= 1<<WR_WAIT_RDY;
        end
      end
      state[WR_ACK]: begin
        wb_ack <= 1'b0;
        state <= 1<<IDLE;
      end
      // Instruction read operation
      state[EX_WAIT_RDY]: begin
        if (i_app_rdy) begin
          app_en <= 1'b1;
          app_cmd <= 3'b001;
          state <= 1<<EX_EN;
        end
      end
      state[EX_EN]: begin
        app_en <= 1'b0;
        if (i_app_rdy) begin
          state <= 1<<EX_WAIT_DV;
        end else begin
          state <= 1<<EX_WAIT_RDY;
        end
      end
      state[EX_WAIT_DV]: begin
        if (i_app_rd_data_valid) begin
          icache      [{app_addr[9:6],icache_block_no}] <= i_app_rd_data;
          icache_tag  [{app_addr[9:6],icache_block_no}] <= app_addr[30:10];
          icache_valid[{app_addr[9:6],icache_block_no}] <= 1'b1;
          instr_ack <= 1'b1;
          instr_data <= i_app_rd_data[wb_adr*32+:32];
          state <= 1<<IDLE;
        end
      end
    endcase
  end
end

endmodule
